package cn.rocksolid.sport.common.utils;

import cn.rocksolid.sport.common.Constant.Symbol;
import cn.rocksolid.sport.common.error.RSE;
import cn.rocksolid.sport.common.error.RockSolidException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;

import java.net.URL;
import java.util.Collection;
import java.util.Date;
import java.util.regex.Pattern;

/**
 * Assert tools
 *
 * @author Axl Zhao
 * @email axl.zhao@163.com
 */
public class AssertUtils {

  private static final Pattern PASSWORD_PATTERN = Pattern.compile("^[A-Za-z0-9_\\-~!@#$%^&*(){}<>\\[\\]]{6,16}$");
  private static final Pattern IDCARD_PATTERN =
          Pattern.compile("^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$");
  private static final Pattern MOBILE_PATTERN = Pattern.compile("^1[34578][0-9]\\d{4,8}$");
  private static final Pattern EMAIL_PATTERN = Pattern.compile("^[.a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$");

  private AssertUtils() {
  }

  public static void isPayload(final Object payload) {
    AssertUtils.isNotNull(payload, RSE.INVALID_PAYLOAD);
  }

  /**
   * validate the give url is valid
   *
   * @param url   url string
   * @param error error for thrown
   */
  public static void isUrl(final String url, final RSE error) {
    isUrl(url, true, error);
  }

  /**
   * validate the give url is valid
   *
   * @param url      url string
   * @param required is required field
   * @param error    error for thrown
   */
  public static void isUrl(final String url, final boolean required, final RSE error) {
    if ((required || StringUtils.isNotBlank(url)) && ((required && StringUtils.isBlank(url)) || !isUrl(url))) {
      invalid(error);
    }
  }

  /**
   * Asserting the given Date/Time is not null, and if minimum or maximum is not
   * null then it should be suitable for that
   *
   * @param date  datetime
   * @param min   min of limit
   * @param max   max of limit
   * @param error error for thrown
   */
  public static void isDate(final Date date, final Date min, final Date max, final RSE error) {
    isDate(date, true, min, max, error);
  }

  /**
   * Asserting the given Date/Time is not null, and if minimum or maximum is not
   * null then it should be suitable for that
   *
   * @param date     datetime
   * @param required is required field
   * @param min      min of limit
   * @param max      max of limit
   * @param error    error for thrown
   */
  public static void isDate(final Date date, final boolean required, final Date min, final Date max, final RSE error) {
    if ((required || null != date) && ((required && null == date) || (null != min && date.compareTo(min) < 0) ||
                                       (null != max && date.compareTo(max) > 0))) {
      invalid(error);
    }
  }

  /**
   * Asserting the given number is not null, and if minimum or maximum is not null
   * then it should be suitable for that
   *
   * @param number given number
   * @param min    min of limit
   * @param max    max of limit
   * @param error  error for thrown
   */
  public static <N extends Number> void isNumber(final N number, final N min, final N max, final RSE error) {
    isNumber(number, true, min, max, error);
  }

  /**
   * Asserting the given number is not null, and if minimum or maximum is not null
   * then it should be suitable for that
   *
   * @param number   given number
   * @param required is required field
   * @param min      min of limit
   * @param max      max of limit
   * @param error    error for thrown
   */
  public static <N extends Number> void isNumber(final N number, final boolean required, final N min, final N max,
          final RSE error) {
    if ((required || null != number) &&
        ((required && null == number) || (null != min && number.doubleValue() < min.doubleValue()) ||
         (null != max && number.doubleValue() > max.doubleValue()))) {
      invalid(error);
    }
  }

  /**
   * Asserting the given array is not null, and if minimum or maximum is not null
   * then it should be suitable for that
   *
   * @param array given array
   * @param min   min elements of limit
   * @param max   max elements of limit
   * @param error error for thrown
   */
  public static <T> void isArray(final T[] array, final Integer min, final Integer max, final RSE error) {
    isArray(array, true, min, max, error);
  }

  /**
   * Asserting the given array is not null, and if minimum or maximum is not null
   * then it should be suitable for that
   *
   * @param array    given array
   * @param required is required field
   * @param min      min elements of limit
   * @param max      max elements of limit
   * @param error    error for thrown
   */
  public static <T> void isArray(final T[] array, final boolean required, final Integer min, final Integer max,
          final RSE error) {
    validCollection(array, required, min, max, error);
  }

  /**
   * Asserting the given collection is not null, and if minimum or maximum is not
   * null then it should be suitable for that
   *
   * @param c     given collection
   * @param min   min elements of limit
   * @param max   max elements of limit
   * @param error error for thrown
   */
  public static void isCollection(final Collection<?> c, final Integer min, final Integer max, final RSE error) {
    isCollection(c, true, min, max, error);
  }

  /**
   * Asserting the given collection is not null, and if minimum or maximum is not
   * null then it should be suitable for that
   *
   * @param c        given collection
   * @param required is required field
   * @param min      min elements of limit
   * @param max      max elements of limit
   * @param error    error for thrown
   */
  public static void isCollection(final Collection<?> c, final boolean required, final Integer min, final Integer max,
          final RSE error) {
    validCollection(c, required, min, max, error);
  }

  /**
   * [Simple] Asserting the given collection or array is not null, and if minimum
   * or maximum is not null then it should be suitable for that
   *
   * @param c        given collection
   * @param required is required field
   * @param min      min elements of limit
   * @param max      max elements of limit
   * @param error    error for thrown
   */
  private static void validCollection(final Object c, final boolean required, final Integer min, final Integer max,
          final RSE error) {
    int sizeOfCollection = CollectionUtils.size(c);
    if ((required || null != c) && ((required && 1 > sizeOfCollection) || (null != min && sizeOfCollection < min) ||
                                    (null != max && sizeOfCollection > max))) {
      invalid(error);
    }
  }

  /**
   * Asserting the given character sequence is common name for the RockSolid
   * system, which length is between 2 and 64
   *
   * @param charSequence given char sequence
   * @param error        error for thrown
   */
  public static void isName(final CharSequence charSequence, final RSE error) {
    isName(charSequence, true, error);
  }

  /**
   * Asserting the given character sequence is common name for the RockSolid
   * system, which length is between 1 and 64
   *
   * @param charSequence given char sequence
   * @param required     is required field
   * @param error        error for thrown
   */
  public static void isName(final CharSequence charSequence, final boolean required, final RSE error) {
    isPrintable(charSequence, required, 1, 64, error);
  }

  /**
   * Asserting the given character sequence is common description for the
   * RockSolid system, which length is between 4 and 1024
   *
   * @param charSequence given char sequence
   * @param error        error for thrown
   */
  public static void isText(final CharSequence charSequence, final RSE error) {
    isText(charSequence, false, error);
  }

  /**
   * Asserting the given character sequence is common description for the
   * RockSolid system, which length is between 4 and 1024
   *
   * @param charSequence given char sequence
   * @param required     is required field
   * @param error        error for thrown
   */
  public static void isText(final CharSequence charSequence, final boolean required, final RSE error) {
    isCoding(charSequence, required, 4, 1024, error);
  }

  /**
   * Asserting the given character sequence is a GUID/UUID format with 32bytes
   *
   * @param charSequence given char sequence
   * @param error        error for thrown
   */
  public static void isUuid32(final CharSequence charSequence, final RSE error) {
    isUuid32(charSequence, true, error);
  }

  /**
   * Asserting the given character sequence is a GUID/UUID format with 32bytes
   *
   * @param charSequence given char sequence
   * @param required     is required field
   * @param error        error for thrown
   */
  public static void isUuid32(final CharSequence charSequence, final boolean required, final RSE error) {
    isBase64(charSequence, required, 32, 32, error);
  }

  /**
   * Asserting the given character sequence is common password for the RockSolid
   * system, which length is between 6 and 16
   *
   * @param charSequence given char sequence
   * @param error        error for thrown
   */
  public static void isPassword(final CharSequence charSequence, final RSE error) {
    isPassword(charSequence, true, error);
  }

  /**
   * Asserting the given character sequence is common password for the RockSolid
   * system, which length is between 6 and 16
   *
   * @param charSequence given char sequence
   * @param required     is required field
   * @param error        error for thrown
   */
  public static void isPassword(final CharSequence charSequence, final boolean required, final RSE error) {
    isPatternValidCharSequence(charSequence, required, PASSWORD_PATTERN, error);
  }

  /**
   * Asserting if the given social identity number is correct.
   *
   * @param idCard given id card
   * @param error  error for thrown
   */
  public static void isIdCard(final String idCard, final RSE error) {
    isIdCard(idCard, true, error);
  }

  /**
   * Asserting if the given social identity number is correct.
   *
   * @param idCard   given id card
   * @param required is required field
   * @param error    error for thrown
   */
  public static void isIdCard(final String idCard, final boolean required, final RSE error) {
    isPatternValidCharSequence(idCard, required, IDCARD_PATTERN, error);
  }

  /**
   * Asserting if the given mobile number is correct.
   *
   * @param mobile given mobile
   * @param error  error for thrown
   */
  public static void isMobile(final String mobile, final RSE error) {
    isMobile(mobile, true, error);
  }

  /**
   * Asserting if the given mobile number is correct.
   *
   * @param mobile   given mobile
   * @param required is required field
   * @param error    error for thrown
   */
  public static void isMobile(final String mobile, final boolean required, final RSE error) {
    isPatternValidCharSequence(mobile, required, MOBILE_PATTERN, error);
  }

  /**
   * Asserting if the given email is correct.
   *
   * @param email given email
   * @param error error for thrown
   */
  public static void isEmail(final String email, final RSE error) {
    isEmail(email, true, error);
  }

  /**
   * Asserting if the given email is correct.
   *
   * @param email    email address
   * @param required is required field
   * @param error    error for thrown
   */
  public static void isEmail(final String email, final boolean required, final RSE error) {
    isPatternValidCharSequence(email, required, EMAIL_PATTERN, error);
  }

  /**
   * Asserting the given character sequence can be pass the given regex pattern
   * matching, and if regex pattern is null then it always pass through.
   *
   * @param charSequence given char sequence
   * @param required     is required field
   * @param pattern      pattern for validate char sequence
   * @param error        error for thrown
   */
  public static void isPatternValidCharSequence(final CharSequence charSequence, final boolean required,
          final Pattern pattern, final RSE error) {
    if ((required || StringUtils.isNotBlank(charSequence)) && ((required && StringUtils.isBlank(charSequence)) ||
                                                               (null != pattern &&
                                                                !pattern.matcher(charSequence).matches()))) {
      invalid(error);
    }
  }

  /**
   * Asserting the given character sequence is all of are base 64 characters.
   *
   * @param charSequence given char sequence
   * @param error        error for thrown
   */
  public static void isBase64(final CharSequence charSequence, final RSE error) {
    isBase64(charSequence, null, null, error);
  }

  /**
   * Asserting the given character sequence is all of are base 64 characters, and
   * if minimum or maximum is not null then it should be suitable for that.
   *
   * @param charSequence given char sequence
   * @param min          min chars of limit
   * @param max          max chars of limit
   * @param error        error for thrown
   */
  public static void isBase64(final CharSequence charSequence, final Integer min, final Integer max, final RSE error) {
    isBase64(charSequence, true, min, max, error);
  }

  /**
   * Asserting the given character sequence is all of are base 64 characters, and
   * if minimum or maximum is not null then it should be suitable for that.
   *
   * @param charSequence given char sequence
   * @param required     is required field
   * @param min          min chars of limit
   * @param max          max chars of limit
   * @param error        error for thrown
   */
  public static void isBase64(final CharSequence charSequence, final boolean required, final Integer min,
          final Integer max, final RSE error) {
    if ((required || StringUtils.isNotBlank(charSequence)) &&
        ((required && !Base64.isBase64((null == charSequence ? Symbol.EMPTY : charSequence.toString()))) ||
         (null != min && StringUtils.length(charSequence) < min) ||
         (null != max && StringUtils.length(charSequence) > max))) {
      invalid(error);
    }
  }

  /**
   * Asserting the given character sequence is all of are alphabetic characters.
   *
   * @param charSequence given char sequence
   * @param error        error for thrown
   */
  public static void isAlphanum(final CharSequence charSequence, final RSE error) {
    isAlphanum(charSequence, null, null, error);
  }

  /**
   * Asserting the given character sequence is all of are alphabetic characters,
   * and if minimum or maximum is not null then it should be suitable for that.
   *
   * @param charSequence given char sequence
   * @param min          min chars of limit
   * @param max          max chars of limit
   * @param error        error for thrown
   */
  public static void isAlphanum(final CharSequence charSequence, final Integer min, final Integer max,
          final RSE error) {
    isAlphanum(charSequence, true, min, max, error);
  }

  /**
   * Asserting the given character sequence is all of are alphabetic characters,
   * and if minimum or maximum is not null then it should be suitable for that.
   *
   * @param charSequence given char sequence
   * @param required     is required field
   * @param min          min chars of limit
   * @param max          max chars of limit
   * @param error        error for thrown
   */
  public static void isAlphanum(final CharSequence charSequence, final boolean required, final Integer min,
          final Integer max, final RSE error) {
    if ((required || StringUtils.isNotBlank(charSequence)) &&
        (!StringUtils.isAlphanumeric(charSequence) || (null != min && StringUtils.length(charSequence) < min) ||
         (null != max && StringUtils.length(charSequence) > max))) {
      invalid(error);
    }
  }

  /**
   * Asserting the given character sequence is printable characters.
   *
   * @param charSequence given char sequence
   * @param error        error for thrown
   */
  public static void isPrintable(final CharSequence charSequence, final RSE error) {
    isPrintable(charSequence, null, null, error);
  }

  /**
   * Asserting the given character sequence is printable characters, and if
   * minimum or maximum is not null then it should be suitable for that.
   *
   * @param charSequence given char sequence
   * @param min          min chars of limit
   * @param max          max chars of limit
   * @param error        error for thrown
   */
  public static void isPrintable(final CharSequence charSequence, final Integer min, final Integer max,
          final RSE error) {
    isPrintable(charSequence, true, min, max, error);
  }

  /**
   * Asserting the given character sequence is printable characters, and if
   * minimum or maximum is not null then it should be suitable for that.
   *
   * @param charSequence given char sequence
   * @param required     is required field
   * @param min          min chars of limit
   * @param max          max chars of limit
   * @param error        error for thrown
   */
  public static void isPrintable(final CharSequence charSequence, final boolean required, final Integer min,
          final Integer max, final RSE error) {
    if ((required || StringUtils.isNotBlank(charSequence)) &&
        (!isPrintableCharacter(charSequence) || (null != min && StringUtils.length(charSequence) < min) ||
         (null != max && StringUtils.length(charSequence) > max))) {
      invalid(error);
    }
  }

  /**
   * Asserting the given character sequence is coding characters.
   *
   * @param charSequence given char sequence
   * @param error        error for thrown
   */
  public static void isCoding(final CharSequence charSequence, final RSE error) {
    isCoding(charSequence, null, null, error);
  }

  /**
   * Asserting the given character sequence is coding characters, and if minimum
   * or maximum is not null then it should be suitable for that.
   *
   * @param charSequence given char sequence
   * @param min          min chars of limit
   * @param max          max chars of limit
   * @param error        error for thrown
   */
  public static void isCoding(final CharSequence charSequence, final Integer min, final Integer max, final RSE error) {
    isCoding(charSequence, true, min, max, error);
  }

  /**
   * Asserting the given character sequence is coding characters, and if minimum
   * or maximum is not null then it should be suitable for that.
   *
   * @param charSequence given char sequence
   * @param required     is required field
   * @param min          min chars of limit
   * @param max          max chars of limit
   * @param error        error for thrown
   */
  public static void isCoding(final CharSequence charSequence, final boolean required, final Integer min,
          final Integer max, final RSE error) {
    if ((required || StringUtils.isNotBlank(charSequence)) &&
        (!isCodingCharacter(charSequence) || (null != min && StringUtils.length(charSequence) < min) ||
         (null != max && StringUtils.length(charSequence) > max))) {
      invalid(error);
    }
  }

  /**
   * Asserting the string is not blank.
   *
   * @param str   given string
   * @param error error for thrown
   */
  public static void isNotBlank(final String str, final RSE error) {
    isTrue(StringUtils.isNotBlank(str), true, error);
  }

  /**
   * Asserting the string is blank.
   *
   * @param str   given string
   * @param error error for thrown
   */
  public static void isBlank(final String str, final RSE error) {
    isTrue(StringUtils.isBlank(str), true, error);
  }

  /**
   * Asserting the object is a non-null pointer.
   *
   * @param object given object
   * @param error  error for thrown
   */
  public static void isNotNull(final Object object, final RSE error) {
    isNotNull(object, true, error);
  }

  /**
   * Asserting the object is a non-null pointer.
   *
   * @param object   given object
   * @param required is required filed
   * @param error    error for thrown
   */
  public static void isNotNull(final Object object, final boolean required, final RSE error) {
    isTrue(null != object, required, error);
  }

  /**
   * Asserting the object is a null pointer.
   *
   * @param object given object
   * @param error  error for thrown
   */
  public static void isNull(final Object object, final RSE error) {
    isNull(object, true, error);
  }

  /**
   * Asserting the object is a null pointer.
   *
   * @param object   given object
   * @param required is required filed
   * @param error    error for thrown
   */
  public static void isNull(final Object object, final boolean required, final RSE error) {
    isTrue(null == object, required, error);
  }

  /**
   * Asserting the condition is true
   *
   * @param condition given bool condition
   * @param error     error for thrown
   */
  public static void isTrue(final Boolean condition, final RSE error) {
    isTrue(condition, true, error);
  }

  /**
   * Asserting the condition is true
   *
   * @param condition given bool condition
   * @param required  is required field
   * @param error     error for thrown
   */
  public static void isTrue(final Boolean condition, final boolean required, final RSE error) {
    if ((required || null != condition) && ((required && null == condition) || !condition)) {
      invalid(error);
    }
  }

  private static void invalid(final RSE error) {
    throw new RockSolidException(error);
  }

  /**
   * Determine the given character sequence is coding characters
   *
   * @param cs given char sequence
   *
   * @return bool
   */
  private static boolean isCodingCharacter(final CharSequence cs) {
    if (cs == null) {
      return false;
    }
    final int sz = cs.length();
    for (int i = 0; i < sz; i++) {
      if (!Character.isLetterOrDigit(cs.charAt(i)) && cs.charAt(i) != ' ' &&
          !CharUtils.isAsciiPrintable(cs.charAt(i)) && CharUtils.LF != cs.charAt(i) && CharUtils.CR != cs.charAt(i)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Determine the given character sequence is printable characters
   *
   * @param cs given char sequence
   *
   * @return bool
   */
  private static boolean isPrintableCharacter(final CharSequence cs) {
    if (cs == null) {
      return false;
    }
    final int sz = cs.length();
    for (int i = 0; i < sz; i++) {
      if (!Character.isLetterOrDigit(cs.charAt(i)) && cs.charAt(i) != ' ' &&
          !CharUtils.isAsciiPrintable(cs.charAt(i))) {
        return false;
      }
    }
    return true;
  }

  /**
   * Determine the given URL is valid
   *
   * @param url given url
   *
   * @return bool
   */
  private static boolean isUrl(final String url) {
    try {
      new URL(url);
      return true;
    } catch (Exception exp) {
      // here will be ignored
    }
    return false;
  }
}
